package com.gtja.beoms.utils;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.gtja.beoms.anno.TimeAnnotation;
import com.gtja.beoms.base.CommonConstants;
import com.gtja.beoms.service.IUploadFileService;
import com.tencent.wework.Finance;
import com.xxl.job.core.context.XxlJobHelper;
import org.apache.commons.lang.StringUtils;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.client.RedisClient;
import org.redisson.config.Config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.io.File;
import java.io.FileOutputStream;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

/**
 * @author LI
 * @description 会话存档 企业微信API交互
 **/
@Component
public class FinanceUtils {

    @Autowired
    private IUploadFileService iUploadFileService;

    private Logger log = LoggerFactory.getLogger(FinanceUtils.class);

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Value("${proxy_on_off}")
    private boolean proxyOnOff;

    @Value("${proxy_ip}")
    private String proxyIp;

    @Value("${proxy_host}")
    private String proxyHost;

    /**
     * 本地保存路径
     */
    private String downloadWeWorkPath = "/usr/local/omsfile/";

    /**
     * 代理设置
     */
    private String proxy = StringUtils.EMPTY;

    /**
     * 代理密码
     */
    private String passWd = StringUtils.EMPTY;

    /**
     * 超时时间，单位秒
     */
    private static final long TIMEOUT = 20;

    /**
     * 只针对mixed类型的特殊处理 把符合类型的content转成string 避免ES报错
     */
    private static boolean jsonStrFlg = false;

    /**
     * 目前OMS能处理的消息类型 其余类型消息一律过滤
     */
    private static final List<String> msgTypeList = Arrays.asList("text", "image", "voice", "video", "emotion",
            "file", "mixed", "meeting_voice_call", "voip_doc_share");

    /**
     * 初始化
     *
     * @param corpId 企业id
     * @param secret 会话存档密钥
     */
    public long initSDK(String corpId, String secret) {
        long sdk = Finance.NewSdk();
        long ret = Finance.Init(sdk, corpId, secret);
        XxlJobHelper.log("当前ret是[{}]");
        if (ret != 0) {
            Finance.DestroySdk(sdk);
            XxlJobHelper.log("sdk初始化失败[{}]:", sdk);
            return 0;
        }
        XxlJobHelper.log("sdk初始化成功[{}]:", sdk);
        return sdk;
    }

    /**
     * 销毁SDK
     */
    public void destroySDK(long sdk) {
        Finance.DestroySdk(sdk);
    }


    /**
     * 拉取聊天记录
     * @param sdk           初始化后的SDK
     * @param corpId        企业微信的idK
     * @param keyEnv        seqKey在不同环境中的前缀
     * @param limit         拉去消息的条数
     * @param resList       会话存档内容List
     * @param privateKeyMap 私钥Map
     * @param redissonClient 分布式redis组件
     */
    public long getChatData(long sdk, String corpId, String keyEnv, long limit, List<JSONObject> resList, Map<Long, String> privateKeyMap, RedissonClient redissonClient) {
        //初始的seq
        long seq = 0;
        //加锁
        RLock seqLock = redissonClient.getLock("SeqLock");
        //尝试上锁
        boolean isLock = false;
        JSONArray chatdataArr = null;
        //加锁 读取seq 根据seq拉腾讯数据 修改seq
        try {
            isLock = seqLock.tryLock(100, 50, TimeUnit.SECONDS);
            if (isLock) {
                String seqKey = keyEnv + "_" + corpId + "_" + "chatSeq";
                String seqSizeKey = keyEnv + corpId + "chatSeqSize";
                //读取seq
                String seqStr = redisTemplate.opsForValue().get(seqKey);
                log.info("本次请求获取消息记录开始的seqStr值: seqStr[{}]", seqStr);
                seq = StringUtils.isNotEmpty(seqStr) ? Long.valueOf(seqStr) : 0L;
                // 设置代理开关
                if (proxyOnOff) {
                    proxy = CommonConstants.HTTP + proxyIp + ":" + proxyHost;
                }
                proxy = "http://172.16.0.11:3128";
                long slice = Finance.NewSlice();
                int ret = Finance.GetChatData(sdk, seq, limit, proxy, passWd, TIMEOUT, slice);
                if (ret != 0) {
                    Finance.FreeSlice(slice);
                    log.info("getChatData ret " + ret);
                    return seq;
                }
                String content = Finance.GetContentFromSlice(slice);
                log.info("获取到的会话存档内容和IP提示[{}]:" + content);
                //释放参数slice
                Finance.FreeSlice(slice);
                chatdataArr = JSONObject.parseObject(content).getJSONArray("chatdata");
                //拿到最后的一个object 拿到seq
                JSONObject jsonObject = (JSONObject) chatdataArr.get(chatdataArr.size() - 1);
                Integer lastSeq = jsonObject.getInteger("seq");
                redisTemplate.opsForValue().set(seqKey, String.valueOf(lastSeq));
                redisTemplate.expire(seqKey, 365, TimeUnit.DAYS);
                //本次seq的数据量
                redisTemplate.opsForValue().set(seqSizeKey, String.valueOf(lastSeq - seq));
                redisTemplate.expire(seqSizeKey, 365, TimeUnit.DAYS);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if (seqLock.isLocked()) {
                seqLock.unlock();
            }
        }


        log.info("开始执行数据解析:------------");
        AtomicLong LocalSEQ = new AtomicLong();
        if (!CollectionUtils.isEmpty(chatdataArr)) {
            for (int i = 0; i < chatdataArr.size(); i++) {
                try {
                    JSONObject data = (JSONObject) chatdataArr.get(i);
                    LocalSEQ.set(data.getLong("seq"));
                    //因为会话存档的公私钥可以进行变更 并且不是随着密钥变更进行变更的 所以去取指定版本的私钥
                    Long privateKeyVer = data.getLong("publickey_ver");
                    String privateKey = privateKeyMap.get(privateKeyVer);
                    JSONObject jsonObject = decryptChatRecord(sdk, data.getString("encrypt_random_key"),
                            data.getString("encrypt_chat_msg"), privateKey);
                    // 当前会话信息如果是切换企业日志 或者不在OMS处理范围的消息直接过滤掉
                    if (jsonObject == null
                            || "switch".equals(jsonObject.getString("action"))
                            || !msgTypeList.contains(jsonObject.getString("msgtype"))) {
                        continue;
                    }
                    jsonObject.put("seq", LocalSEQ.get());
                    resList.add(jsonObject);
                } catch (Exception e) {
                    e.printStackTrace();
                    return LocalSEQ.get();
                }

            }
        }
        log.info("数据解析结束:------------");
        return seq;
    }

    private void getSwitchType(long sdk, JSONObject realJsonData, String msgType) {
        switch (msgType) {
            case "image":
                setMediaImageData(sdk, realJsonData, msgType);
                break;
            case "voice":
                setMediaVoiceData(sdk, realJsonData, msgType);
                break;
            case "video":
                setMediaVideoData(sdk, realJsonData, msgType);
                break;
            case "emotion":
                setMediaEmotionData(sdk, realJsonData, msgType);
                break;
            case "file":
                setMediaFileData(sdk, realJsonData, msgType);
                break;
            case "mixed":
                jsonStrFlg = true;
                try {
                    setMediaMixedData(sdk, realJsonData, msgType);
                } catch (Exception e) {
                    XxlJobHelper.log("复合类型出错：{}", e.getMessage());
                } finally {
                    jsonStrFlg = false;
                }
                break;
            case "meeting_voice_call":
                XxlJobHelper.log("进入meeting_voice_call的文件上传");
                try {
                    setMeetingVoiceCallData(sdk, realJsonData, msgType);
                } catch (Exception e) {
                    XxlJobHelper.log("上传meeting_voice_call出错：{}", e.getMessage());
                }
            case "voip_doc_share":
                setMediaMeetingVoiceCallData(sdk, realJsonData, msgType);
                break;
            default:
                break;
        }
    }

    private void setMeetingVoiceCallData(long sdk, JSONObject realJsonData, String msgType) {
        String fileName = IdUtils.simpleUUID() + ".mp3";
        getPath(sdk, realJsonData, msgType, fileName);
        XxlJobHelper.log("会话音频存档存储成功");
    }

    /**
     * @param sdk               初始化时候获取到的值
     * @param ncrypt_random_key 企业微信返回的随机密钥
     * @param encrypt_chat_msg  企业微信返回的单条记录的密文消息
     * @param privateKey        企业微信管理后台设置的私钥,!!!版本记得对应上!!!
     * @return JSONObject 返回不同格式的聊天数据,格式有二十来种
     */
    @TimeAnnotation
    public JSONObject decryptChatRecord(Long sdk, String ncrypt_random_key, String encrypt_chat_msg, String privateKey) throws Exception {
        //初始化参数slice
        long msg = Finance.NewSlice();
        try {
            //获取私钥
            String str = RSAUtil.decryptRSA(ncrypt_random_key, RSAUtil.getPrivateKey(privateKey));
            //解密
            long ret = Finance.DecryptData(sdk, str, encrypt_chat_msg, msg);
            if (ret != 0) {
                Finance.FreeSlice(msg);
                log.info("[ERROR] getchatdata ret", ret);
                return null;
            }
            String jsonDataStr = Finance.GetContentFromSlice(msg);
            //释放参数slice
            Finance.FreeSlice(msg);
            JSONObject realJsonData = JSONObject.parseObject(jsonDataStr);
            String msgType = realJsonData.getString("msgtype");
            if (StringUtils.isNotEmpty(msgType)) {
                getSwitchType(sdk, realJsonData, msgType);
            }
//            log.info("[{}]数据解析:------------[{}]",Thread.currentThread().getName(), realJsonData.toJSONString());
            return realJsonData;
        } catch (Exception e) {
            log.info("[ERROR] 解析密文失败");
            throw e;
        }
    }

    private void setMediaMeetingVoiceCallData(long sdk, JSONObject realJsonData, String msgType) {
        JSONObject meetingVoiceCall = Optional.ofNullable(realJsonData.getJSONObject(msgType))
                .orElse(realJsonData.getJSONObject("content"));
        String fileName = meetingVoiceCall.getString("filename");
        getPath(sdk, realJsonData, msgType, fileName);
    }

    private void setMediaMixedData(long sdk, JSONObject realJsonData, String msgType) {
        JSONObject mixedData = realJsonData.getJSONObject(msgType);
        JSONArray items = mixedData.getJSONArray("item");
        items.stream().map(item -> (JSONObject) item).forEach(item -> {
            getSwitchType(sdk, item, item.getString("type"));
        });
    }

    private void setMediaFileData(long sdk, JSONObject realJsonData, String msgType) {
        JSONObject emotionData = Optional.ofNullable(realJsonData.getJSONObject(msgType))
                .orElse(realJsonData.getJSONObject("content"));
        String filename = emotionData.getString("filename");
        getPath(sdk, realJsonData, msgType, filename);
    }


    private void setMediaImageData(long sdk, JSONObject realJsonData, String msgType) {
        String fileName = IdUtils.simpleUUID() + ".jpg";
        getPath(sdk, realJsonData, msgType, fileName);
    }

    private void setMediaVoiceData(long sdk, JSONObject realJsonData, String msgType) {
        String fileName = IdUtils.simpleUUID() + ".amr";
        getPath(sdk, realJsonData, msgType, fileName);
    }

    private void setMediaVideoData(long sdk, JSONObject realJsonData, String msgType) {
        String fileName = IdUtils.simpleUUID() + ".mp4";
        getPath(sdk, realJsonData, msgType, fileName);
    }

    private void setMediaEmotionData(long sdk, JSONObject realJsonData, String msgType) {
        String fileName = "";
        JSONObject emotionData = realJsonData.getJSONObject(msgType);
        Integer type = emotionData.getInteger("type");
        switch (type) {
            case 1:
                fileName = IdUtils.simpleUUID() + ".gif";
                break;
            case 2:
                fileName = IdUtils.simpleUUID() + ".png";
                break;
            default:
                break;
        }
        getPath(sdk, realJsonData, msgType, fileName);
    }

    //云存储
    private void getPath(long sdk, JSONObject realJsonData, String msgType, String fileName) {
        String localPath = downloadWeWorkPath + msgType;

        JSONObject data = Optional.ofNullable(realJsonData.getJSONObject(msgType))
                .orElse(realJsonData.getJSONObject("content"));
        String sdkfileid = data.getString("sdkfileid");
        try {
            // 先将文件保存到本地
            getMediaData(sdk, sdkfileid, proxy, passWd, localPath, fileName);

            // 将文件上传到FTP
            File file = new File(localPath, fileName);
            if (file != null && file.exists()) {
                String url = iUploadFileService.uploadFile(file);
                data.put("mediaLinkUrl", url);
                if (StringUtils.isNotEmpty(url)) {
                    XxlJobHelper.log("文件FTP上传成功 URL:[{}]", url);
                    // 删除本地文件
                    file.delete();
                    XxlJobHelper.log("本地文件：" + file.getAbsolutePath() + " 删除成功！");
                } else {
                    XxlJobHelper.log("文件FTP上传失败 本地文件URL:[{}]", localPath + fileName);
                }
            } else {
                XxlJobHelper.log("文件本地上传失败 sdkfileid:[{}]", sdkfileid);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (realJsonData.containsKey("content")) {
            realJsonData.put("content", jsonStrFlg ? data.toJSONString() : data);
        } else {
            realJsonData.put(msgType, jsonStrFlg ? data.toJSONString() : data);
        }

    }

    /**
     * @param sdkFileid
     * @param proxy
     * @param passwd
     * @param filePath
     * @param fileName
     * @deprecated 企业微信媒体资源处理
     */
    private void getMediaData(long sdk, String sdkFileid, String proxy, String passwd, String filePath, String
            fileName) {
        String indexbuf = "";
        while (true) {
            long media_data = Finance.NewMediaData();
            int ret = Finance.GetMediaData(sdk, indexbuf, sdkFileid, proxy, passwd, TIMEOUT, media_data);
            //XxlJobHelper.log("getMediaData ret:" + ret);
            if (ret != 0) {
                Finance.FreeMediaData(media_data);
                return;
            }
            try {
                File f = new File(filePath);
                if (!f.exists()) {
                    f.mkdirs();
                }
                File file = new File(filePath, fileName);
                if (!file.isDirectory()) {
                    file.createNewFile();
                }
                FileOutputStream outputStream = new FileOutputStream(file, true);
                outputStream.write(Finance.GetData(media_data));
                outputStream.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
            if (Finance.IsMediaDataFinish(media_data) == 1) {
                Finance.FreeMediaData(media_data);
                break;
            } else {
                indexbuf = Finance.GetOutIndexBuf(media_data);
                Finance.FreeMediaData(media_data);
            }
        }
    }


    /**
     * 企微用户客户机器判断
     *
     * @param str
     * @return
     */
    public String weCustomTypeJudgment(String str) {
        if (StringUtils.isNotEmpty(str)) {
            //外部联系人的external_userid以”wo”或”wm”开头。
            if (str.startsWith("wo") || str.startsWith("wm")) {
                return CommonConstants.CONTACTS_TYPE_CUSTOMER;
            }
            //机器人的external_userid是以”wb”开头
            if (str.startsWith("wb")) {
                return CommonConstants.CONTACTS_TYPE_ROBOT;
            }
        }
        return CommonConstants.CONTACTS_TYPE_MEMBER;
    }


    /**
     * 辅助生成 ffmpeg 的 amr 转 mp3 命令
     */
    public String getAmrToMp3Cmd(String amrPath, String outMp3Path) {
        // MAC 系统下 需要 ffmpeg 的完整路径
        return "ffmpeg -i " + amrPath + " " + outMp3Path;
    }

}
